package org.nf.web.servlet;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.nf.web.config.Configure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

/**
 * @author wangl
 * @date 2021/5/24
 * 请求总控的Servlet，它将接收所有的http请求，
 * 然后交给HandlerMapping根据请求地址查找合适的Handler（控制器），
 * 再将handler交给匹配的HandlerAdapter来执行handler处理请求，
 * 最后返回相应的视图进行渲染
 */
public class DispatcherServlet extends HttpServlet {

    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 请求映射处理器集合
     */
    private final List<HandlerMapping> handlerMappings = new ArrayList<>();

    /**
     * 请求回调处理器集合
     */
    private final List<HandlerAdapter> handlerAdapters = new ArrayList<>();

    /**
     * 全局异常处理器集合
     */
    private final List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();

    /**
     * 通过扫描的类初始化所有的
     * HandlerMapping、HandlerAdapter、HandlerExceptionResolver、HandlerInterceptor
     */
    @Override
    public void init(ServletConfig config) {
        //通过ServletContext中获取配置信息
        Configure configure = (Configure) config.getServletContext().getAttribute(Configure.class.getName());
        //扫描Class
        scan(configure);
        //初始化HandlerMappings
        initHandlerMappings();
        //初始化HandlerAdapters
        initHandlerAdapters();
        //初始化全局异常解析器
        initHandlerExceptionResolvers();
        //初始化所有拦截器
        initHandlerInterceptors();
    }

    /**
     * 从配置对象中获取扫描的包路径进行扫描将Class集合保存到FrameworkContext
     */
    private void scan(Configure configure) {
        String[] basePackages = configure.getMvc().getScan().getBasePackages();
        Set<Class<?>> classSet = new HashSet<>();
        for(String basePackage : basePackages) {
            Set<Class<?>> classes = ClassUtil.scanPackage(basePackage);
            classSet.addAll(classes);
        }
        //将扫描的类保存到FrameworkContext中便于下一步初始化操作
        FrameworkContext.getInstance().setClasses(classSet);
    }

    /**
     * 初始化请求映射处理器
     * 初始化用户自定义以及框架的HandlerMapping
     */
    private void initHandlerMappings() {
        FrameworkContext.getInstance().getClasses().stream()
                .filter(HandlerMapping.class::isAssignableFrom)
                .forEach(handlerMappingClass -> {
                    HandlerMapping handlerMapping = (HandlerMapping) ReflectUtil.newInstance(handlerMappingClass);
                    handlerMappings.add(handlerMapping);
                });
        for (HandlerMapping handlerMapping : ServiceLoader.load(HandlerMapping.class)) {
            //初始化HandlerMapping并保存到集合中
            handlerMappings.add(handlerMapping);
        }
    }

    /**
     * 初始化请求适配处理器
     * 初始化用户自定义以及框架的HandlerAdapter
     */
    private void initHandlerAdapters() {
        FrameworkContext.getInstance().getClasses().stream().filter(HandlerAdapter.class::isAssignableFrom)
                .forEach(handlerAdapterClass -> {
                    HandlerAdapter handlerAdapter = (HandlerAdapter) ReflectUtil.newInstance(handlerAdapterClass);
                    handlerAdapters.add(handlerAdapter);
                });
        ServiceLoader.load(HandlerAdapter.class).forEach(handlerAdapters::add);
    }

    /**
     * 初始化全局异常处理器
     * 初始化用户自定义以及框架的HandlerExceptionResolver
     */
    private void initHandlerExceptionResolvers() {
        FrameworkContext.getInstance().getClasses().stream()
                .filter(HandlerExceptionResolver.class::isAssignableFrom)
                .forEach(exceptionResolverClass -> {
                    HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver) ReflectUtil.newInstance(exceptionResolverClass);
                    exceptionResolvers.add(handlerExceptionResolver);
                });
        for (HandlerExceptionResolver handlerExceptionResolver : ServiceLoader.load(HandlerExceptionResolver.class)) {
            exceptionResolvers.add(handlerExceptionResolver);
        }
    }

    /**
     * 找出所有拦截器的Class保存到Servlet上下文
     */
    private void initHandlerInterceptors() {
        List<Class<?>> interceptors = FrameworkContext.getInstance().getClasses().stream()
                .filter(HandlerInterceptor.class::isAssignableFrom)
                .toList();
        HandlerInterceptorRegistration registration = new HandlerInterceptorRegistration();
        registration.addInterceptors(interceptors);
        FrameworkContext.getInstance().setInterceptorRegistration(registration);
    }

    @Override
    protected void service(HttpServletRequest req,
                           HttpServletResponse resp) throws ServletException, IOException {
        //构建WebParam参数，封装request和response
        ServletApi servletApi = new ServletApi(req, resp);
        //使用HandlerMapping查找相应的Handler处理请求并返回一个HandlerExecutionChain
        HandlerExecutionChain chain = getHandler(req);
        //如果没找到
        if (chain == null) {
            return;
        }
        //视图对象，接收请求处理后返回的视图结果
        BaseView view = null;
        Exception exception = null;
        try {
            //获取一个能够处理处理当前handler的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(chain.getHandler());
            //执行拦截器链的方法preHandle方法,如果返回false则退出请求处理
            if (!chain.applyPreHandle(servletApi)) {
                return;
            }
            //将handler交给HandlerAdapter执行调用并返回视图结果
            view = ha.handle(chain.getHandler(), servletApi);
            //执行拦截器链的方法postHandle方法
            chain.applyPostHandle(servletApi, view);
        } catch (Exception e) {
            exception = e;
        }
        //处理返回结果
        processDispatcherResult(view, servletApi, chain, exception);
    }

    /**
     * 遍历HandlerMapping集合，其中某个handlerMapping找到合适的handler,
     * 此时的handler已经是封装好的HandlerExecutionChain
     *
     * @param request 请求对象
     * @return HandlerExecutionChain
     */
    private HandlerExecutionChain getHandler(HttpServletRequest request) throws ServletException {
        for (HandlerMapping handlerMapping : handlerMappings) {
            HandlerExecutionChain chain = handlerMapping.getHandler(request);
            if (chain != null) {
                return chain;
            }
        }
        return null;
    }

    /**
     * 根据handler找到合适的HandlerAdapter
     *
     * @param handler 可以处理请求的handler
     * @return HandlerAdapter（适配器）
     */
    private HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new ServletException("No suitable HandlerAdapter.");
    }

    /**
     * 处理返回结果
     */
    private void processDispatcherResult(BaseView view, ServletApi servletApi, HandlerExecutionChain chain, Throwable e) throws ServletException, IOException {
        if (e != null) {
            //异常处理
            view = handleException(e);
        }
        //渲染视图
        render(view, servletApi);
        //产生异常时执行拦截器链的方法afterCompletion方法
        chain.triggerAfterCompletion(servletApi, e);
    }

    /**
     * 将异常交给全局异常解析器处理
     *
     * @param e 请求处理过程引发的异常
     * @return 视图
     */
    private BaseView handleException(Throwable e) {
        log.debug(e.getMessage(), e);
        //获取根异常对象（真正引发错误的异常对象）
        e = ExceptionUtil.getRootCause(e);
        //目标方法产生异常时则将交给相应的异常解析器处理
        BaseView view = null;
        for (HandlerExceptionResolver exceptionResolver : exceptionResolvers) {
            view = exceptionResolver.resolveException(e);
            if (view != null) {
                break;
            }
        }
        return view;
    }

    /**
     * 执行视图渲染
     *
     * @param view       视图对象
     * @param servletApi 请求和响应对象
     */
    private void render(BaseView view, ServletApi servletApi) throws ServletException, IOException {
        if (view != null) {
            view.render(servletApi);
        }
    }
}
